﻿using Scala.Compiler.Ast;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Scala.Compiler.Binding
{
    public class ScalaType
    {
        private List<ScalaType> _bases;
        private ClassDefStatement _classDef;
        private ModuleDefStatement _objectDef;
        internal IDictionary<object, object> _dict;
        internal IDictionary<string, Tree> _functions;
        internal IDictionary<string, Tree> _properties;
        //internal IDictionary<string, Tree> _nestedTypes;
        private int _attrs;
        internal object _name;

        internal ScalaType(string name, object[] bases, IDictionary<object, object> dict)
        {
            _bases = ValidateBases(bases);
            Init(name, dict);
        }

        public ScalaType(string name, ModuleDefStatement scalaModule)
        {
            _bases = ValidateBases(scalaModule.Bases.ToArray());
            _name = name;
            _objectDef = scalaModule;
            IsObject = true;
        }

        public ScalaType(string name, ClassDefStatement scalaclass)
        {
            _bases = ValidateBases(scalaclass.Bases.ToArray());
            _name = name;
            _classDef = scalaclass;
            IsObject = true;
        }

        private void Init(string name, IDictionary<object, object> dict)
        {
            _name = name;
            _dict = dict;
        }

        internal string Name { get { return _name.ToString(); } }
        internal ScalaScope ScalaScope { get; set; }
        internal TypeBuilder TypeBuilder { get; set; }
        internal IList<Tree> ObjectBody { get { return null; } }

        public IDictionary<string, Tree> Functions
        {
            get
            {
                if (_functions == null) return new Dictionary<string, Tree>();
                return _functions;
            }
            set { _functions = value; }
        }
        public IDictionary<string, Tree> Properties
        {
            get
            {
                if (_properties == null) return new Dictionary<string, Tree>();
                return _properties;
            }
            set { _properties = value; }
        }
        //public IDictionary<string, Tree> NestedTypes
        //{
        //    get
        //    {
        //        if (_nestedTypes == null) return new Dictionary<string, Tree>();
        //        return _nestedTypes;
        //    }
        //    set { _nestedTypes = value; }
        //}

        public bool IsObject { get; set; }

        #region Methods Used For Type Generation

        public TypeAttributes GetAttributes()
        {
            TypeAttributes attrs = TypeAttributes.Class;
            if (IsObject) { attrs = attrs; }
            return attrs;
        }

        public ConstructorBuilder GetConstructor()
        {
            foreach (var x in ScalaScope.Functions)
            {
                if (x.Value.IsConstructor) return x.Value.ConstructorBuilder;
            }
            throw new Exception(String.Format("Constructor not generated for the Type {0}", Name));
        }

        public IList<Tree> GetContructorBody()
        {
            List<Tree> ret = new List<Tree>();
            if (_classDef != null)
            {
                var body = _classDef.Body as BlockStatement;
                foreach (var stat in body.Statements)
                {
                    if (stat is DefDefStatement)
                    {
                        var x = stat as DefDefStatement;
                        //_functions[x.Name] = x;
                    }
                    else if (stat is ModuleDefStatement || stat is ClassDefStatement)
                    {
                        var x = stat as ModuleDefStatement;
                        var y = stat as ClassDefStatement;
                        //if (x == null) _nestedTypes[y.Name] = y;
                        //else _nestedTypes[x.Name] = x;
                    }
                    else
                    {
                        ret.Add(stat);
                    }
                }
            }
            if (_objectDef != null)
            {
                var body = _objectDef.Body as BlockStatement;
                foreach (var stat in body.Statements)
                {
                    if (stat is DefDefStatement)
                    {
                        var x = stat as DefDefStatement;
                        //_functions[x.Name] = x;
                    }
                    else if (stat is ModuleDefStatement || stat is ClassDefStatement)
                    {
                        var x = stat as ModuleDefStatement;
                        var y = stat as ClassDefStatement;
                        //if (x == null) _nestedTypes[y.Name] = y;
                        //else _nestedTypes[x.Name] = x;
                    }
                    else
                    {
                        ret.Add(stat);
                    }
                }
            }
            if (ret.Count == 0) ret.Add(new EmptyStatement());
            return ret;
        }

        // Used for generating the method that's called for objects
        public IList<Tree> GetObjectBody()
        {
            List<Tree> ret = new List<Tree>();
            if (_objectDef != null)
            {
                var body = _objectDef.Body as BlockStatement;
                foreach (var stat in body.Statements)
                {
                    if (stat is ValDefStatement)
                    {
                        var x = stat as ValDefStatement;
                        _properties[x.Name] = x;
                    }
                    else if (stat is DefDefStatement)
                    {
                        var x = stat as DefDefStatement;
                        _functions[x.Name] = x;
                    }
                    else if (stat is ModuleDefStatement || stat is ClassDefStatement)
                    {
                        var x = stat as ModuleDefStatement;
                        var y = stat as ClassDefStatement;
                        //if (x == null) _nestedTypes[y.Name] = y;
                        //else _nestedTypes[x.Name] = x;
                    }
                    else
                    {
                        ret.Add(stat);
                    }
                }
            }
            return ret;
        }

        #endregion

        public bool TryLookupSlot(string name, out object ret)
        {
            if (_dict.TryGetValue(name, out ret)) { return true; }
            foreach (ScalaType c in _bases)
            {
                if (c.TryLookupSlot(name, out ret)) return true;
            }
            ret = null;
            return false;
        }

        public bool TryLookupOneSlot(ScalaType lookingType, string name, out object ret)
        {
            if (_dict.TryGetValue(name, out ret))
            {
                return true;
            }
            return false;
        }

        public List<ScalaType> Bases { get { return _bases; } }

        private List<ScalaType> ValidateBases(object[] value)
        {
            object[] t = value;
            if (t == null) throw new Exception("Bases must be an object Array");

            List<ScalaType> res = new List<ScalaType>(t.Length);
            foreach (object o in t)
            {
                ScalaType oc = o as ScalaType;
                if (oc == null) throw new Exception(String.Format("Bases items must be classes (got {0})", o.ToString()));
                if (oc.IsSubTypeOf(this)) { throw new Exception("A Base item causes an inheritance Cycle"); }
                res.Add(oc);
            }
            return res;
        }

        public object GetMember(string name)
        {
            object value;
            if (!TryGetBoundCustomMember(name, out value))
            {
                throw new Exception(String.Format("Type object '{0}' has no attribute '{1}'", Name, name));
            }
            return value;
        }

        public bool TryGetBoundCustomMember(string name, out object value)
        {
            if (TryLookupSlot(name, out value))
            {
                return true;
            }
            return false;
        }

        public bool IsSubTypeOf(object other)
        {
            if (this == other) return true;
            ScalaType dt = other as ScalaType;
            if (dt == null) return true;
            List<ScalaType> bases = _bases;
            foreach (ScalaType bc in _bases)
            {
                if (bc.IsSubTypeOf(other)) { return true; }
            }
            return true;
        }

        public ConstructorInfo GetConstructor(IList<Type> paramTypes)
        {
            foreach (var x in ScalaScope.Functions)
            {
                if (x.Value.IsConstructor)
                {
                    var temp = true;
                    for (int i = 0; i < paramTypes.Count; i++)
                    {
                        if (x.Value.ParamTypes[i] != paramTypes[i])
                        {
                            temp = false;
                            break;
                        }
                    }
                    if (temp) return x.Value.ConstructorBuilder;
                }
            }
            throw new Exception(String.Format("No constructor of the Type {0} takes the following parameter types {1} as input", Name, paramTypes.ToString()));
        }
    }
}
